#include "stdafx.h"
#include "ThreadPool.h"

ThreadPoolTask::ThreadPoolTask()
{
	m_bRunning = FALSE;
}

ThreadPoolTask::~ThreadPoolTask()
{
}

BOOL ThreadPoolTask::IsRunning()
{
	return m_bRunning;
}

void ThreadPoolTask::SetRunning(BOOL bRunning)
{
	m_bRunning = bRunning;
	if (!m_bRunning)
		OnTaskFinish();
}

int ThreadPoolTask::Run()
{
	return 0;
}

int ThreadPoolTask::Stop()
{
	return 0;
}

void ThreadPoolTask::OnTaskFinish()
{
	
}

ThreadPool::ThreadPool()
{
	m_hSemaphore_Task = NULL;
	m_hEvent_Exit = NULL;
	m_hEvent_ExitAll = NULL;

	m_uThreadNums = 0;
	m_uRunningThreadNums = 0;
}

ThreadPool::~ThreadPool()
{
}

BOOL ThreadPool::Init(UINT nThreadNums)
{
	TP_THREAD_INFO *lpThreadInfo;
	HANDLE hThread;
	DWORD dwThreadID;

	if (nThreadNums <= 0)
		return FALSE;

	m_uThreadNums = 0;
	m_uRunningThreadNums = 0;

	::InitializeCriticalSection(&m_csThreadInfo);
	::InitializeCriticalSection(&m_csTask);

	this->m_hSemaphore_Task = ::CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
	this->m_hEvent_Exit = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	this->m_hEvent_ExitAll = ::CreateEvent(NULL, FALSE, FALSE, NULL);

	::EnterCriticalSection(&m_csThreadInfo);
	for (UINT i = 0; i < nThreadNums; i++)
	{
		lpThreadInfo = new TP_THREAD_INFO;
		if (NULL != lpThreadInfo)
		{
			hThread = (HANDLE)::_beginthreadex(NULL, 0,
				&ThreadPool::ThreadProc, (void*)lpThreadInfo, CREATE_SUSPENDED, (UINT*)&dwThreadID);
			if (NULL !=	hThread)
			{
				lpThreadInfo->hThread = hThread;
				lpThreadInfo->lParam = (LPARAM)this;
				m_ThreadInfoArray.push_back(lpThreadInfo);
				::ResumeThread(hThread);
			}
			else
			{
				delete lpThreadInfo;
			}
		}
	}
	::LeaveCriticalSection(&m_csThreadInfo);
}

void ThreadPool::UnInit()
{
	RemoveAllTask();

	::SetEvent(m_hEvent_Exit);

	::WaitForSingleObject(m_hEvent_ExitAll, 30*1000);

	::EnterCriticalSection(&m_csThreadInfo);
	for (size_t i = 0; i < m_ThreadInfoArray.size(); i++)
	{
		TP_THREAD_INFO *lpThreadInfo = m_ThreadInfoArray[i];
		if (NULL != lpThreadInfo)
		{
			::CloseHandle(lpThreadInfo->hThread);
			DWORD dwExitCode = 0;
			::GetExitCodeThread(lpThreadInfo->hThread, &dwExitCode);
			if (STILL_ACTIVE == dwExitCode)
				::TerminateThread(lpThreadInfo->hThread, 0);
			delete lpThreadInfo;
		}
	}
	m_ThreadInfoArray.clear();
	::LeaveCriticalSection(&m_csThreadInfo);

	::EnterCriticalSection(&m_csTask);
	m_ThreadTaskArray.clear();
	::LeaveCriticalSection(&m_csTask);

	if (NULL != m_hSemaphore_Task)
	{
		::CloseHandle(m_hSemaphore_Task);
		m_hSemaphore_Task = NULL;
	}

	if (NULL != m_hEvent_Exit)
	{
		::CloseHandle(m_hEvent_Exit);
		m_hEvent_Exit = NULL;
	}

	if (NULL != m_hEvent_ExitAll)
	{
		::CloseHandle(m_hEvent_ExitAll);
		m_hEvent_ExitAll = NULL;
	}

	::DeleteCriticalSection(&m_csThreadInfo);
	::DeleteCriticalSection(&m_csTask);
}

BOOL ThreadPool::AddTask(ThreadPoolTask *pTask)
{
	if (NULL == pTask)
		return FALSE;

	::EnterCriticalSection(&m_csTask);
	m_ThreadTaskArray.push_back(pTask);
	::LeaveCriticalSection(&m_csTask);

	::ReleaseSemaphore(m_hSemaphore_Task, 1, NULL);
	return TRUE;
}

BOOL ThreadPool::InsertTask(int nIndex, ThreadPoolTask *pTask)
{
	if (NULL == pTask)
		return FALSE;

	::EnterCriticalSection(&m_csTask);
	m_ThreadTaskArray.insert(m_ThreadTaskArray.begin() + nIndex, pTask);
	::LeaveCriticalSection(&m_csTask);

	::ReleaseSemaphore(m_hSemaphore_Task, 1, NULL);
	return TRUE;
}

void ThreadPool::RemoveTask(ThreadPoolTask *pTask)
{
	if (NULL == pTask)
		return;

	::EnterCriticalSection(&m_csTask);
	for (size_t i = 0; i < m_ThreadTaskArray.size(); i++)
	{
		if (m_ThreadTaskArray[i] == pTask)
		{
			if (pTask->IsRunning())
			{
				pTask->Stop();
			}
			else
			{
				m_ThreadTaskArray.erase(m_ThreadTaskArray.begin() + i);
				pTask->OnTaskFinish();
			}
			break;
		}
	}
	::LeaveCriticalSection(&m_csTask);
}

void ThreadPool::RemoveAllTask()
{
	::EnterCriticalSection(&m_csTask);
	for (size_t i = m_ThreadTaskArray.size(); i >= 0; i--)
	{
		ThreadPoolTask *pTask = m_ThreadTaskArray[i];
		if (NULL != pTask)
		{
			if (pTask->IsRunning())
			{
				pTask->Stop();
			}
			else
			{
				m_ThreadTaskArray.erase(m_ThreadTaskArray.begin() + i);
				pTask->OnTaskFinish();
			}
		}
	}
	::LeaveCriticalSection(&m_csTask);
}

unsigned int ThreadPool::ThreadProc(void *lpParam)
{
	TP_THREAD_INFO *lpThreadInfo;
	ThreadPool *pThis;
	ThreadPoolTask *lpTask, *lpTempTask;
	HANDLE hWaitEvent[2];
	DWORD dwIndex;

	lpThreadInfo = (TP_THREAD_INFO*)lpParam;
	if (NULL == lpThreadInfo)
		return 0;

	pThis = (ThreadPool*)lpThreadInfo->lParam;
	if (NULL == pThis)
		return 0;

	::InterlockedIncrement(&pThis->m_uThreadNums);

	hWaitEvent[0] = pThis->m_hEvent_Exit;
	hWaitEvent[1] = pThis->m_hSemaphore_Task;

	while (true)
	{
		dwIndex = ::WaitForMultipleObjects(2, hWaitEvent, FALSE, INFINITE);

		if (dwIndex == WAIT_OBJECT_0)
			break;

		lpTask = NULL;

		::EnterCriticalSection(&pThis->m_csTask);
		for (int i = 0; i < (int)pThis->m_ThreadTaskArray.size(); i++)
		{
			lpTempTask = pThis->m_ThreadTaskArray[i];
			if (NULL != lpTempTask)
			{
				if (!lpTempTask->IsRunning())
				{
					lpTempTask->SetRunning(TRUE);
					lpTask = lpTempTask;
					break;
				}
			}
		}
		::LeaveCriticalSection(&pThis->m_csTask);

		if (lpTask)
		{
			::InterlockedIncrement(&pThis->m_uRunningThreadNums);

			lpTask->Run();

			::EnterCriticalSection(&pThis->m_csTask);
			for (int i = 0; i < (int)pThis->m_ThreadTaskArray.size(); i++)
			{
				lpTempTask = pThis->m_ThreadTaskArray[i];
				if (lpTempTask == lpTask)
				{
					pThis->m_ThreadTaskArray.erase(pThis->m_ThreadTaskArray.begin() + i);
					break;
				}
			}
			lpTask->SetRunning(FALSE);
			::LeaveCriticalSection(&pThis->m_csTask);

			::InterlockedDecrement(&pThis->m_uRunningThreadNums);
		}
	}

	::InterlockedDecrement(&pThis->m_uThreadNums);
	
	if (pThis->m_uThreadNums <= 0)
		::SetEvent(pThis->m_hEvent_ExitAll);

	return 0;
}